package com.ww.seckill.controller;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wf.captcha.ArithmeticCaptcha;
import com.ww.seckill.config.AccessLimit;
import com.ww.seckill.entity.Order;
import com.ww.seckill.entity.SeckillMessage;
import com.ww.seckill.entity.SeckillOrder;
import com.ww.seckill.entity.User;
import com.ww.seckill.exception.GlobalException;
import com.ww.seckill.rabbitmq.MqSender;
import com.ww.seckill.service.IGoodsService;
import com.ww.seckill.service.IOrderService;
import com.ww.seckill.service.ISeckillOrderService;
import com.ww.seckill.vo.GoodsVo;
import com.ww.seckill.vo.RespBean;
import com.ww.seckill.vo.RespBeanEnum;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Controller
@RequestMapping("/seckill")
public class SeckillController implements InitializingBean {

    @Autowired
    private IGoodsService goodsService;
    @Autowired
    private ISeckillOrderService seckillOrderService;
    @Autowired
    private IOrderService orderService;
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    private MqSender mqSender;
    @Autowired
    private RedisScript<Long> script;

    private Map<Long, Boolean> emptyStockMap = new HashMap<>();


    /**
     * 初始化加载
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVo> goodsVos = goodsService.getGoodsVo();
        if (goodsVos.size() == 0) {
            return;
        }
        ValueOperations valueOperations = redisTemplate.opsForValue();
        for (GoodsVo goodsVo : goodsVos) {
            valueOperations.set("secKillGoods:" + goodsVo.getId(), goodsVo.getStockCount());
            emptyStockMap.put(goodsVo.getId(), false);
        }
    }

    /**
     * windows优化前QPS 785.9
     * 缓存后QPS 1350
     * 优化后QPS 2450
     * linux优化前QPS 170
     */
    @RequestMapping("/doSecKill")
    public String doSecKill(Model model, User user, Long goodsId) {
        if (user == null) {
            return "login";
        }
        model.addAttribute("user", user);
        GoodsVo goodsVo = goodsService.getGoodsVoById(goodsId);
        model.addAttribute("goods", goodsVo);
        // 判断库存
        if (goodsVo.getStockCount() <= 0) {
            model.addAttribute("errMsg", RespBeanEnum.EMPTY_STOCK.getMessage());
            return "secKillFail";
        }
        // 判断当前用户是否已经秒杀过 (这里可以判断redis值)
//        SeckillOrder seckillOrder = (SeckillOrder)redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);
        QueryWrapper<SeckillOrder> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", user.getId());
        wrapper.eq("goods_id", goodsId);
        SeckillOrder seckillOrder = seckillOrderService.getOne(wrapper);
        if (seckillOrder != null) {
            model.addAttribute("errmsg", RespBeanEnum.HAS_SECKILL.getMessage());
            return "secKillFail";
        }
        // 下订单
        Order order = orderService.secKill(user, goodsVo);
        model.addAttribute("order", order);
        return "orderDetail";
    }

    @RequestMapping("/doSecKill_V2")
    @ResponseBody
    public RespBean doSecKill_V2(Model model, User user, Long goodsId) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.USER_TIME_OUT);
        }

        ValueOperations valueOperations = redisTemplate.opsForValue();
        // 判断当前用户是否已经秒杀过
        SeckillOrder seckillOrder = (SeckillOrder) valueOperations.get("order:" + user.getId() + ":" + goodsId);
        if (seckillOrder != null) {
            return RespBean.error(RespBeanEnum.HAS_SECKILL);
        }
        // 内存标记,减少Redis的访问
        if (emptyStockMap.get(goodsId)) {
            return RespBean.error(RespBeanEnum.EMPTY_STOCK);
        }
        // 预减库存
        // Long stock = valueOperations.decrement("secKillGoods:" + goodsId);
        Long stock = (Long) redisTemplate.execute(script, Collections.singletonList("secKillGoods:" + goodsId), Collections.EMPTY_LIST);
        if (stock < 0) {
            emptyStockMap.put(goodsId, true);
            valueOperations.increment("secKillGoods:" + goodsId);
            return RespBean.error(RespBeanEnum.EMPTY_STOCK);
        }
        // 下订单
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
        mqSender.sendSecKillMessage(JSON.toJSONString(seckillMessage));

        return RespBean.success(0);
    }

    /**
     * 查询秒杀结果
     *
     * @param user    用户
     * @param goodsId 商品编号
     * @return 返回订单ID
     */
    @RequestMapping("/result")
    @ResponseBody
    public RespBean getResult(User user, String goodsId) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.USER_TIME_OUT);
        }

        String key = "order:" + user.getId() + ":" + goodsId;
        ValueOperations valueOperations = redisTemplate.opsForValue();
        Long orderId = -1L;
        if (valueOperations.get(key) != null) {
            SeckillOrder seckillOrder = (SeckillOrder) valueOperations.get(key);
            orderId = seckillOrder.getOrderId();
        } else if ((int) valueOperations.get("secKillGoods:" + goodsId) <= 0) {
            orderId = -1L;
        } else {
            orderId = 0L;
        }
        return RespBean.success(orderId);
    }

    /**
     * 生成验证码
     *
     * @param user
     * @param goodsId
     * @param response
     */
    @RequestMapping("/captcha")
    public void captcha(User user, Long goodsId, HttpServletResponse response) {
        if (user == null || goodsId < 0) {
            throw new GlobalException(RespBeanEnum.ERROR);
        }

        // 设置请求头为输出图片数据
        response.setContentType("image/jpg");
        response.setHeader("Pargam", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        // 生成验证码，将结果放入redis中
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 32, 3);
        redisTemplate.opsForValue().set("captcha:" + user.getId() + ":" + goodsId, captcha.text(), 300, TimeUnit.SECONDS);
        try {
            captcha.out(response.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 获取秒杀地址
     *
     * @param user
     * @param goodsId
     * @param captcha
     * @param request
     * @return
     */
    @RequestMapping("/path")
    @ResponseBody
    @AccessLimit(second = 5, maxCount = 5, needLogin = true)
    public RespBean getPath(User user, Long goodsId, String captcha, HttpServletRequest request) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.USER_TIME_OUT);
        }

        // 限制访问次数，5秒内访问5次 (存在问题：)
        ValueOperations valueOperations = redisTemplate.opsForValue();
        String uri = request.getRequestURI();
        Integer count = (Integer) valueOperations.get(uri + ":" + user.getId());
        if (count == null) {
            valueOperations.set(uri + ":" + user.getId(), 1, 5, TimeUnit.SECONDS);
        } else if (count < 5) {
            valueOperations.increment(uri + ":" + user.getId());
        } else {
            return RespBean.error(RespBeanEnum.REQUEST_FAST);
        }

        //判断验证码是否正确
        Boolean check = orderService.checkCptcha(user, goodsId, captcha);
        if (!check) {
            return RespBean.error(RespBeanEnum.ERROR_CAPTCHA);
        }

        //生成秒杀的接口
        String str = orderService.createPath(user, goodsId);
        return RespBean.success(str);
    }


    @RequestMapping("/{path}/doSeckill")
    @ResponseBody
    public RespBean doSecKill(@PathVariable String path, User user, Long goodsId) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.USER_TIME_OUT);
        }

        ValueOperations valueOperations = redisTemplate.opsForValue();
        Boolean check = orderService.checkPath(user, path, goodsId);
        if (!check) {
            return RespBean.error(RespBeanEnum.PATH_ERROR);
        }

        // 判断当前用户是否已经秒杀过
        SeckillOrder seckillOrder = (SeckillOrder) valueOperations.get("order:" + user.getId() + ":" + goodsId);
        if (seckillOrder != null) {
            return RespBean.error(RespBeanEnum.HAS_SECKILL);
        }
        if (emptyStockMap.get(goodsId)) {
            return RespBean.error(RespBeanEnum.EMPTY_STOCK);
        }

/*        redis预减库存
        Long stock = valueOperations.decrement("seckillGoods:" + goodsId);*/
        // 使用lua脚本
        Long stock = (Long) redisTemplate.execute(script, Collections.singletonList("secKillGoods:" + goodsId), Collections.EMPTY_LIST);
        if (stock == 0) {
            emptyStockMap.put(goodsId, true);
            return RespBean.error(RespBeanEnum.EMPTY_STOCK);
        }

        //下订单
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
        mqSender.sendSecKillMessage(JSON.toJSONString(seckillMessage));
        return RespBean.success(0);
    }


}
